<?php
/**
 * Copyright (c) 2008, Dardo Sordi
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright        Copyright (c) 2008, Dardo Sordi
 * @license        http://www.opensource.org/licenses/mit-license.php The MIT License
 * @author    Dardo Sordi
 */

class SortableBehavior extends ModelBehavior {

    var $__config = array();

    function setup(&$model, $config = array()) {
        $default = array('field' => 'order', 'enabled' => true, 'group' => false);
        $this->__config[$model->name] = array_merge($default, (array)$config);
    }

    function _config(&$model, $config = array()) {
        if (is_array($config)) {
            if (!empty($config)) {
                $this->__config[$model->name] = array_merge($this->__config[$model->name], $config);
            }
        } else {
            return $this->__config[$model->name][$config];
        }

        return $this->__config[$model->name];
    }

    function _fullField(&$model) {
        return $model->name . '.' . $this->_config($model, 'field');
    }

    function beforeFind(&$model, $conditions = array()) {
        extract($this->_config($model));
        if (!$enabled) {
            return $conditions;
        }
        if (is_string($conditions['fields']) && strpos($conditions['fields'], 'COUNT') === 0) {
            return $conditions;
        }
        $order = $conditions['order'];
        if (is_array($conditions['order'])) {
            $order = current($conditions['order']);
        }
        if (empty($order)) {
            $conditions['order'] = array(array($this->_fullField($model) => 'ASC'));

            if ($group !== false) {
                $conditions['order'] = array_merge(array($group => 'ASC'), $conditions['order']);
            }
        }
        return $conditions;
    }

    function beforeSave(&$model) {
        extract($this->_config($model));
        if ($enabled) {
            $fixPosition = false;
            $isInsert = !$model->id;
            if (isset($model->data[$model->name][$field]) && !empty($model->data[$model->name][$field])) {
                $newPosition = $model->data[$model->name][$field];
            } else {
                $newPosition = null;
            }
            $groupId = $this->_groupId($model);
            $model->data[$model->name][$field] = $this->lastPosition($model, $groupId) + 1;
            $model->__fixPosition = $newPosition;
        }
        return true;
    }

    function afterSave(&$model, $created) {
        extract($this->_config($model));
        if (!$enabled) {
            return true;
        }
        $position = $model->data[$model->name][$field];
        if ($model->__fixPosition) {
            $position = $model->__fixPosition;
            $model->__fixPosition = null;
            $this->setPosition($model, $model->id, $position);
        }
    }

    function beforeDelete(&$model) {
        extract($this->_config($model));
        if ($enabled) {
            $model->__fixPosition = $this->position($model);
        }
        return true;
    }

    function afterDelete(&$model) {
        extract($this->_config($model));
        if (!$enabled) {
            return true;
        }
        $fullField = $this->_fullField($model);
        $position = $model->__fixPosition;
        $model->__fixPosition = null;
        $model->updateAll(array($fullField => "$fullField - 1"), array("$fullField >=" => $position));
    }

    function moveTop(&$model, $id = null) {
        $this->disableSortable($model);
        if ($id) {
            $model->id = $id;
        }
        extract($this->_config($model));
        $position = $this->position($model);
        $groupId = $this->_groupId($model);
        $fullField = $this->_fullField($model);

        if ($position > 1) {
            $newPosition = 1;
            $conditions = $this->_conditions($model, $groupId, array("$fullField <=" => $position, "$fullField >=" => $newPosition));
            $model->updateAll(array($fullField => "$fullField + 1"), $conditions);
            $model->saveField($field, $newPosition);
        }
        $this->enableSortable($model);
        return true;
    }

    function moveUp(&$model, $id = null, $step = 1) {
        $this->disableSortable($model);
        if ($id) {
            $model->id = $id;
        }
        extract($this->_config($model));
        $position = $this->position($model);
        $groupId = $this->_groupId($model);
        $fullField = $this->_fullField($model);

        if ($position > 1) {
            $newPosition = $position - $step;
            if ($newPosition < 1) {
                $newPosition = 1;
            }
            $conditions = $this->_conditions($model, $groupId, array("$fullField <=" => $position, "$fullField >=" => $newPosition));
            $model->updateAll(array($fullField => "$fullField + 1"), $conditions);
            $model->saveField($field, $newPosition);
        }
        $this->enableSortable($model);
        return true;
    }

    function moveDown(&$model, $id = null, $step = 1) {
        $this->disableSortable($model);
        if ($id) {
            $model->id = $id;
        }
        extract($this->_config($model));
        $position = $this->position($model);
        $groupId = $this->_groupId($model);
        $id = $model->id;
        $model->id = null;
        $last = $this->lastPosition($model, $groupId);
        $fullField = $this->_fullField($model);

        if ($position < $last) {
            $newPosition = $position + $step;
            if ($newPosition > $last) {
                $newPosition = $last;
            }
            $conditions = $this->_conditions($model, $groupId, array("$fullField >=" => $position, "$fullField <=" => $newPosition));
            $model->updateAll(array($fullField => "$fullField - 1"), $conditions);
            $model->id = $id;
            $model->saveField($field, $newPosition);
        }
        $this->enableSortable($model);
        return true;
    }

    function moveBottom(&$model, $id = null) {
        $this->disableSortable($model);
        if ($id) {
            $model->id = $id;
        }
        extract($this->_config($model));

        $position = $this->position($model);
        $groupId = $this->_groupId($model);
        $id = $model->id;
        $model->id = null;
        $last = $this->lastPosition($model, $groupId);
        $fullField = $this->_fullField($model);

        if ($position < $last) {
            $newPosition = $last;
            $conditions = $this->_conditions($model, $groupId, array("$fullField >=" => $position, "$fullField <=" => $newPosition));
            $model->updateAll(array($fullField => "$fullField - 1"), $conditions);
            $model->id = $id;
            $model->saveField($field, $newPosition);
        }
        $this->enableSortable($model);
        return true;
    }

    function setPosition(&$model, $id = null, $destination) {
        $this->disableSortable($model);
        if ($id) {
            $model->id = $id;
        }
        extract($this->_config($model));
        $position = $this->position($model);
        $id = $model->id;
        $model->id = null;
        $delta = $position - $destination;

        if ($position > $destination) {
            $this->moveUp($model, $id, $delta);
        } elseif ($position < $destination) {
            $this->moveDown($model, $id, -$delta);
        }
        $this->enableSortable($model);
        return true;
    }

    function disableSortable(&$model) {
        $this->_config($model, array('enabled' => false));
    }

    function enableSortable(&$model) {
        $this->_config($model, array('enabled' => true));
    }

    function position(&$model, $id = null) {
        if ($id) {
            $model->id = $id;
        }
        return $model->field($this->_config($model, 'field'));
    }

    function _groupId(&$model, $id = null) {
        $group = $this->_config($model, 'group');

        if ($group === false) {
            return null;
        }
        if ($id) {
            $model->id = $id;
        }
        return $model->field($group);
    }

    function lastPosition(&$model, $groupId = null) {
        $id = $model->id;
        $model->id = null;
        $field = $this->_config($model, 'field');
        $fields = array($field);
        $order = array($field => 'DESC');
        $conditions = $this->_conditions($model, $groupId);
        $last = $model->find('first',  compact('fields', 'order', 'conditions'));
        $model->id = $id;

        if (!empty($last)) {
            return current(current($last));
        }

        return false;
    }

    function _conditions(&$model, $groupId = null, $conditions = array()) {
        $group = $this->_config($model, 'group');

        if (($group !== false) && !is_null($groupId)) {
            $conditions = array_merge($conditions, array($group => $groupId));
        }

        return $conditions;
    }

    function findByPosition(&$model, $position, $groupId = null) {
        $field = $this->_fullField($model);
        return $model->find($this->_conditions($model, $groupId, array($field => $position)));
    }

}

?> 